package cn.kbyue.security.security;

import cn.kbyue.security.constants.CachePrefixEnum;
import cn.kbyue.security.entity.KbUser;
import cn.kbyue.security.http.MultiReadHttpServletRequest;
import cn.kbyue.security.http.MultiReadHttpServletResponse;
import cn.kbyue.security.utils.CacheLocalUtil;
import cn.kbyue.security.utils.JwtProperties;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.CollectionType;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * 每次访问请求时，检验token
 *
 * @author xl
 * @date 2020/4/19 17:16
 */
@Slf4j
@Component
public class CustomTokenFilter extends OncePerRequestFilter {

    @Resource
    private CustomAuthenticationEntryPoint unLoginEntryPoint;

    /**
     * 拦截每次请求，验证token
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 如果是登录请求，可以添加验证码 验证

        log.info(">>> CustomTokenFilter 实现访问鉴权");
        log.info(">>> 请求头类型：" + request.getContentType());

        // 转换 request response, 以多次使用
        MultiReadHttpServletRequest wrappedRequest = new MultiReadHttpServletRequest(request);
        MultiReadHttpServletResponse wrappedResponse = new MultiReadHttpServletResponse(response);
        StopWatch stopWatch = new StopWatch();

        try {
            stopWatch.start();
            // 记录请求的消息体
            logRequestBody(wrappedRequest);

            // 前后端分离情况下，前端登录后将token储存在cookie中，每次访问接口时通过token去拿用户权限
            String token = wrappedRequest.getHeader(JwtProperties.TOKEN_HEADER);
            log.info("后台检查header AccessToken: {}", token);

            if (!StringUtils.isEmpty(token)) {
                // 解析token
                Claims claims = Jwts.parser()
                        .setSigningKey(JwtProperties.SECRET)
                        .parseClaimsJws(token)
                        .getBody();
                // 获取账号信息
                String account = claims.getSubject();
                String userId = claims.getId();

                // 获取缓存保存的 authorities 信息
                String authority = CacheLocalUtil.get(CachePrefixEnum.LOGIN_USER + userId);
                if (authority == null) {
                    throw new AuthenticationServiceException("无法获取用户信息，请重新登录");
                }
                ObjectMapper mapper = new ObjectMapper();
                Collection<GrantedAuthority> authorities = new ArrayList<>();
                CollectionType listType = mapper.getTypeFactory().constructCollectionType(ArrayList.class, Map.class);
                List<Map<String, Object>> authoritiesJson = mapper.readValue(authority, listType);
                for (Map<String, Object> grantedAuthority : authoritiesJson) {
                    String role = grantedAuthority.get("role").toString();
                    ArrayList<String> permitStr = (ArrayList<String>) grantedAuthority.get("permission");
                    authorities.add(new DefinedGrantedAuthority(role, permitStr));
                }

                KbUser user = new KbUser();
                user.setAccount(account).setUserId(userId);
                DefinedUserDetails securityUser = new DefinedUserDetails(user, authorities);
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
                // 全局注入角色权限信息和登录用户基本信息
                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
            filterChain.doFilter(wrappedRequest, wrappedResponse);
        } catch (ExpiredJwtException e) {
            log.error(">>> token 过期了, message: {}", e.getMessage());
            SecurityContextHolder.clearContext();
            this.unLoginEntryPoint.commence(wrappedRequest, response, null);
        } catch (AuthenticationException ae) {
            log.error(">>> token 解析失败, cause: {}", ae.getMessage());
            SecurityContextHolder.clearContext();
            this.unLoginEntryPoint.commence(wrappedRequest, response, ae);
        } finally {
            stopWatch.stop();
            long usedTimes = stopWatch.getTotalTimeMillis();
            // 记录响应的消息体
            logResponseBody(wrappedRequest, wrappedResponse, usedTimes);
        }
    }

    private void logRequestBody(MultiReadHttpServletRequest request) {
        if (request != null) {
            try {
                String bodyJson = request.getBodyJsonStrByJson(request);
                String url = request.getRequestURI().replace("//", "/");
                System.out.println("-------------------------------- 请求url: " + url + " --------------------------------");
                log.info("`{}` 接收到的参数: {}", url, bodyJson);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private void logResponseBody(MultiReadHttpServletRequest request, MultiReadHttpServletResponse response, long useTime) {
        if (response != null) {
            byte[] buf = response.getBody();
            if (buf.length > 0) {
                String payload;
                try {
                    payload = new String(buf, 0, buf.length, response.getCharacterEncoding());
                } catch (UnsupportedEncodingException ex) {
                    payload = "[unknown]";
                }
                log.info("`{}`  耗时:{}ms  返回的参数: {}", "URL: " + request.getRequestURI(), useTime, payload);
            }
        }
    }

}
